#include <qapplication.h>
#include <qdialog.h>
#include <qpushbutton.h>
#include <qstring.h>
#include <qdebug.h>
#include <QLabel>
#include <QMessageBox>

#include <QFile>

#include "OptimizationEnvironment.h"
#include "parameters.h"
#include "math.h"

#include "TournamentSelectionStrategy.h"
#include "RouletteWheelSelectionStrategy.h"
#include "SimpleSelectionStrategy.h"

#include "SelectionStrategyFactory.h"

double formula(QVector<double> argumentVector);
double formula2(QVector<double> argumentVector);
double formula3(QVector<double> argumentVector);

double deJongF1(QVector<double> argumentVector);
double deJongF1_inv(QVector<double> argumentVector);
double deJongF2(QVector<double> argumentVector);
double deJongF2_inv(QVector<double> argumentVector);
double deJongF3(QVector<double> argumentVector);
double deJongF3_inv(QVector<double> argumentVector);

double deJongF4(QVector<double> argumentVector);
double deJongF4_inv(QVector<double> argumentVector);

double deJongF5(QVector<double> argumentVector);
double deJongF5_sum(QVector<double> argumentVector, int countJ);
double deJongF5_inv(QVector<double> argumentVector);

double test_function_sinc(QVector<double> argumentVector);
double sinc(double x, double y);

double test_function_sin2(QVector<double> argumentVector);
double test_function_easom(QVector<double> argumentVector);

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    QString str = "";



    int popSize = 100;
    int selection = Tournament;//Deterministic;//RouletteWheel;
    bool adjustFit = true;//false;
    double crossProb = 0.9;
    double mutProb = 0.05;
    int tournamentSize = 10;
    bool replaceWorst = true;
    int addFresh = 0;
    int eliminateDuplicates = None;//Replace;
    int runNumber = 100;
    double bestFitness = -0.001;//0.9999;//0;//29.9999;//-1;//25.0;//0;//-1.0;//25.0;//0.0;//-0.01;
    int finishCond = BestFitnessKnown;//GenerationCount;

    int generationCount = 100;
    int precision = 4;

    /*    int popSize = 50;
    int selection = RouletteWheel;
    double crossProb = 0.5;
    double mutProb = 0.5;
    int tournamentSize = 20;
    bool replaceWorst = true;
    int addFresh = 0;
    int eliminateDuplicates = None;
    int runNumber = 100;

    int generationCount = 200;
    int precision = 4;*/

    QString fileName = "optimization.txt";
    QFile* file = new QFile (fileName );

    if ( !file->open ( QIODevice::Append | QIODevice::Text ) )
        qFatal("cannot open file");

    QTextStream stream(file);

    stream << endl;
    stream << endl;

    stream << "popSize: " << popSize;
    stream << " selection: " << selection;
    stream << " adjustFit: " << adjustFit;
    stream << " crossProb: " << crossProb;
    stream << " mutProb: " << mutProb;
    stream << " tournamentSize: " << tournamentSize;
    stream << " replaceWorst: " << replaceWorst;
    stream << " addFresh: " << addFresh;
    stream << " eliminateDuplicates: " << eliminateDuplicates;
    stream << " runNumber: " << runNumber;
    stream << " bestFitness: " << bestFitness;
    stream << " precision: " << precision;
    stream << " generationCount: " << generationCount;
    stream << " finishCond: " << finishCond;
    stream << endl;


    QVector< QPair<double, double> > rangeVector;

    QVector< QPair<double, double> > rangeDeJong1;
    rangeDeJong1.append(QPair<double, double>(-5.12,5.12));
    rangeDeJong1.append(QPair<double, double>(-5.12,5.12));
    rangeDeJong1.append(QPair<double, double>(-5.12,5.12));
    QVector< QPair<double, double> > rangeDeJong2;
    rangeDeJong2.append(QPair<double, double>(-2.048,2.048));
    rangeDeJong2.append(QPair<double, double>(-2.048,2.048));
    QVector< QPair<double, double> > rangeDeJong3;
    rangeDeJong3.append(QPair<double, double>(-5.12,5.12));
    rangeDeJong3.append(QPair<double, double>(-5.12,5.12));
    rangeDeJong3.append(QPair<double, double>(-5.12,5.12));
    rangeDeJong3.append(QPair<double, double>(-5.12,5.12));
    rangeDeJong3.append(QPair<double, double>(-5.12,5.12));
    QVector< QPair<double, double> > rangeDeJong4;
    for(int i=0;i<30;++i)
        rangeDeJong4.append(QPair<double, double>(-1.28,1.28));
    QVector< QPair<double, double> > rangeDeJong5;
    rangeDeJong5.append(QPair<double, double>(-65.536,65.536));
    rangeDeJong5.append(QPair<double, double>(-65.536,65.536));

    QVector< QPair<double, double> > rangeSinc;
    rangeSinc.append(QPair<double,double>(-10, 10));
    rangeSinc.append(QPair<double,double>(-10, 10));

    QVector< QPair<double, double> > rangeSin2;
    rangeSin2.append(QPair<double,double>(0, 1));
    rangeSin2.append(QPair<double,double>(0, 1));

    QVector< QPair<double, double> > rangeEasom;
    rangeEasom.append(QPair<double,double>(-10, 10));
    rangeEasom.append(QPair<double,double>(-10, 10));

   /* rangeVector.append(QPair<double,double>(-M_PI, 2*M_PI+0.5));
    rangeVector.append(QPair<double,double>(-M_PI, 2*M_PI+0.5));*/

    rangeVector.append(QPair<double,double>(-3.0, 12.1));
    rangeVector.append(QPair<double,double>(4.1, 5.8));


    QVector< QPair<double, double> > rVect;
    double (*formulaPtr)(QVector<double>);


    //!!!!!!!!!! set best fit!!!!!!
    rVect = rangeDeJong2;//rangeEasom;//rangeSin2;//rangeDeJong5;//rangeDeJong3;//rangeDeJong2;//rangeDeJong1;
    formulaPtr = deJongF2_inv;//test_function_easom;//test_function_sin2;//deJongF5_inv;//_inv;//_inv;//deJongF3;//deJongF2_inv;//deJongF1_inv;

    stream << "rVect: ";
    for(int t=0;t<rVect.size();++t)
        stream << "(" << rVect.at(t).first << "," << rVect.at(t).second << ")";
    stream << endl;

    //stream << &formulaPtr << endl;


    double sumOfBest = 0;
    int sumOfAge = 0;
    QList<int> ageList;
    QList<double> fitList;

    for(int i=0;i<runNumber;++i)
    {
        SelectionStrategy* selectionStrategy = SelectionStrategyFactory::getStrategy(selection);
        if(selection == Tournament)
            dynamic_cast<TournamentSelectionStrategy*>(selectionStrategy)->setSize(tournamentSize);

        OptimizationEnvironment environment(popSize, rVect, precision, formulaPtr);//(popSize, rangeVector, precision, formula);
        environment.setMaxGenerationsCount(generationCount);
        //environment.setSelectionType(selection);
        //environment.setTournamentSize(tournamentSize);
        environment.setSelectionStrategy(selectionStrategy);
        environment.setMutationProb(mutProb);
        environment.setAddFreshChromosomes(addFresh);
        environment.setRemoveDuplicatesMethod(eliminateDuplicates);
        //if(bestFitness != 0)
        environment.setBestFitness(bestFitness);
        environment.setFinishConditionType(finishCond);

        environment.setAdjustFitness(adjustFit);
        environment.setCrossoverProb(crossProb);
        environment.setReplaceWorstRepresentative(replaceWorst);
        //environment.setAbortWorseGenerations(true);
        environment.startSimulation();

        qDebug() << *environment.getSolution();

        str += environment.getStatisticsString();
        qDebug() << str;



        stream << QString::number(environment.getSolution()->getFitness(), 'f', precision) << "(";
        stream << environment.getAge() << ");";

        sumOfBest += environment.getSolution()->getFitness();
        sumOfAge += environment.getAge();

        ageList.append(environment.getAge());
        fitList.append(environment.getSolution()->getFitness());

        qDebug() << "run: " << runNumber;

        sleep(1);//not working in windows

    }


    stream << endl;
    //stream << "av: " << QString::number(sumOfBest/(double)runNumber, 'f', precision) << endl;

    double average = sumOfAge/(double)runNumber;
    double averageFit = sumOfBest/(double)runNumber;

    //wariancja

    double sumSq = 0;
    for(int a=0;a<ageList.size();++a)
    {
        sumSq += (ageList.at(a) - average) * (ageList.at(a) - average);
    }
    double wariancja = sumSq/runNumber;
    double odchylenie = sqrt(wariancja);

    double sumSqFit = 0;
    for(int a=0;a<fitList.size();++a)
    {
        sumSqFit += (fitList.at(a) - averageFit) * (fitList.at(a) - averageFit);
    }
    double wariancjaFit = sumSqFit/runNumber;
    double odchylenieFit = sqrt(wariancjaFit);


    stream << endl;
    stream << "GEN av: " << average << " wariancja: " << wariancja << " odchylenie: " << odchylenie << endl;
    stream << "FIT av: " << averageFit << " wariancja: " << wariancjaFit << " odchylenie: " << odchylenieFit << endl;

    file->close ();


    /*double a;
    for(int j=1;j<=25;++j)
    {
        QString str("");
        for(int i=0;i<2;++i)
        {
            if(i % 2 == 0)
            {
                a = -32.0+16.0*((j-1)%5);
            }
            else
            {
                a = -32.0+16.0*((j-1)/5);
            }
            str += QString::number(a) + ",";
        }
        qDebug() << str;
    }*/



    QMessageBox* messageBox = new QMessageBox();
    messageBox->setText(str);
    messageBox->show();

    return 0;//app.exec();
}

double formula(QVector<double> argumentVector)
{
        double value;
        int argumentCount = 2;

        if(argumentVector.size() < argumentCount)
            qFatal("Too few arguments!");

        value = 21.5 + argumentVector[0]*sin(4 * M_PI * argumentVector[0]) +
                argumentVector[1]*sin(20 * M_PI * argumentVector[1]);

        return value;
}

double formula2(QVector<double> argumentVector)
{
        double value;
        int argumentCount = 1;

        if(argumentVector.size() < argumentCount)
            qFatal("Too few arguments!");

        value = argumentVector[0]*sin(10 * M_PI * argumentVector[0]) + 1.0;

        return value;
}

double formula3(QVector<double> argumentVector)
{
        double value;
        int argumentCount = 2;

        if(argumentVector.size() < argumentCount)
            qFatal("Too few arguments!");

        value = 20 + argumentVector[0]*sin(2 * M_PI * argumentVector[0]) +
                2*argumentVector[1]*sin(3 * M_PI * argumentVector[1]);

        return value;
}


double deJongF1(QVector<double> argumentVector)
{
    double value = 0.0;
    int argumentCount = 3;

    if(argumentVector.size() < argumentCount)
        qFatal("Too few arguments!");

    for(int i=0;i<argumentCount;++i)
        value += argumentVector[i]*argumentVector[i];

    return value;
}

double deJongF1_inv(QVector<double> argumentVector)
{
    return -deJongF1(argumentVector);
}

double deJongF2(QVector<double> argumentVector)
{
    double value;
    int argumentCount = 2;

    if(argumentVector.size() < argumentCount)
        qFatal("Too few arguments!");

    value = 100*pow(pow(argumentVector[0],2.0)-argumentVector[1],2.0) +
             pow(1-argumentVector[0],2.0);

    return value;
}

double deJongF2_inv(QVector<double> argumentVector)
{
    return -deJongF2(argumentVector);
}

double deJongF3(QVector<double> argumentVector)
{
    double value = 0.0;
    int argumentCount = 5;

    if(argumentVector.size() < argumentCount)
        qFatal("Too few arguments!");

    for(int i=0;i<argumentCount;++i)
        value += floor(argumentVector[i]);

    return value;
}

double deJongF3_inv(QVector<double> argumentVector)
{
    return -deJongF3(argumentVector);
}

double ranf()
{
    return static_cast<double>(qrand()) / static_cast<double>(RAND_MAX);
}

//from http://www.bearcave.com/misl/misl_tech/wavelets/hurst/random.html
double getGaussianDistributionRandom()
{
// from http://www.taygeta.com/random/gaussian.html
// Algorithm by Dr. Everett (Skip) Carter, Jr.

       float x1, x2, w, y1, y2;

       do {
               x1 = 2.0 * ranf() - 1.0;
               x2 = 2.0 * ranf() - 1.0;
               w = x1 * x1 + x2 * x2;
       } while ( w >= 1.0 );

       w = sqrt( (-2.0 * log( w ) ) / w );
       y1 = x1 * w;
       y2 = x2 * w;
       //where ranf() is the routine to obtain a random number uniformly distributed in [0,1).

       return y1;
}

double deJongF4(QVector<double> argumentVector)
{
    double value = 0.0;
    int argumentCount = 30;

    if(argumentVector.size() < argumentCount)
        qFatal("Too few arguments!");

    for(int i=0;i<argumentCount;++i)
        value += i*argumentVector[i]*argumentVector[i]*argumentVector[i]*argumentVector[i];

    return value;
}

double deJongF4_inv(QVector<double> argumentVector)
{
    return -deJongF4(argumentVector);
}

double deJongF5(QVector<double> argumentVector)
{
    double value = 0.002;
    int argumentCount = 2;

    if(argumentVector.size() < argumentCount)
        qFatal("Too few arguments!");

    for(int j=1;j<=25;++j)
        value += 1.0/(j + deJongF5_sum(argumentVector, j));

    return 1/value;
}

double deJongF5_sum(QVector<double> argumentVector, int countJ)
{
    double value = 0.0;
    double a = 0.0;

    for(int i=0;i<argumentVector.size();++i)
    {
        if(i % 2 == 0)
        {
            a = -32.0+16.0*((countJ-1)%5);
        }
        else
        {
            a = -32.0+16.0*((countJ-1)/5);
        }
        value += pow(argumentVector[i] - a ,6.0);
    }

    return value;
}

double deJongF5_inv(QVector<double> argumentVector)
{
    return -deJongF5(argumentVector);
}

double test_function_sinc(QVector<double> argumentVector)
{
    double value = 0;
    int argumentCount = 2;

    if(argumentVector.size() < argumentCount)
        qFatal("Too few arguments!");

    value = 30 * sinc(argumentVector[0], argumentVector[1]) +
            argumentVector[0]*sin(2*argumentVector[0]) +
            argumentVector[1]*sin(2*argumentVector[1]);

    return value;
}

double sinc(double x, double y)
{
    if(x == 0 && y == 0)
        return 1;

    return sin(sqrt(x*x+y*y))/sqrt(x*x+y*y);
}

double test_function_sin2(QVector<double> argumentVector)
{
 //max (0.5,0.5) = 0,87890625
    double value = 0;
    int argumentCount = 2;

    if(argumentVector.size() < argumentCount)
        qFatal("Too few arguments!");

    int n = 9;

    value = pow(15*argumentVector[0]*argumentVector[1]*(1-argumentVector[0])*(1-argumentVector[1])*sin(n*M_PI*argumentVector[0])*sin(n*M_PI*argumentVector[1]),2);

    return value;
}


double test_function_easom(QVector<double> argumentVector)
{
    //max (pi,pi) = 1
    double value = 0;
    int argumentCount = 2;

    if(argumentVector.size() < argumentCount)
        qFatal("Too few arguments!");

    value = cos(argumentVector[0])*cos(argumentVector[1])*exp(-(pow(argumentVector[0]-M_PI,2) + pow(argumentVector[1]-M_PI,2)));

    return value;
}
